package com.google.zahra.soap;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;

import javax.jws.WebService;

import com.google.zahra.soap.data.structs.ChromeBotWorkPacket;
import com.google.zahra.soap.data.structs.QueueSizeRequest;
import com.google.zahra.soap.data.structs.ZahraStructsFactory;

import org.terracotta.collections.ClusteredMap;

@WebService
public class WorkPacketManager {
  private static int max_size = 1 * 1000 * 1000;
  private static final String URL_QUEUE = "chromebot_workqueue";
  private static final String PENDING_PACKETS = "pending_workpackets";

  private static BlockingQueue<byte[]> urls = Server.toolkit.getBlockingQueue(URL_QUEUE, max_size);
  private static ClusteredMap<String, List<Object>> pending = Server.toolkit.getMap(PENDING_PACKETS);

  public WorkPacketManager() {
    
    TimerTask checkPacketTimeouts = new CheckPacketTimeouts();

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(checkPacketTimeouts, 0, 60000);
  }

  public void clearWorkUrls() {
    urls.clear();
  }

  public QueueSizeRequest getSize() {
    return new QueueSizeRequest(urls.size(), max_size);
  }

  public boolean addPacket(final String[] urlList) {
    // We don't need to lock here since there is a buff large enough to
    // handle all in-flight requests to the server and avoid blocking.
    if (urls.size() + 1 >= max_size) {
      return false;
    }
    ChromeBotWorkPacket packet = ZahraStructsFactory.createChromeBotWorkPacket(urlList);
    urls.add(ChromeBotWorkPacket.getByteArray(packet));

    return true;
  }

  public ChromeBotWorkPacket popPacket() {

    byte[] serializedWp = urls.poll();
    ChromeBotWorkPacket wp = ChromeBotWorkPacket.fromByteArray(serializedWp);
    
    if (wp != null) {
      List<Object> list = new ArrayList<Object>();
      list.add(0, new Date());
      list.add(1, serializedWp);
      
      pending.put(wp.getId(), list);
      System.out.println("Popping packet: " + wp.getId());
      return wp;
    }
    else {
      return null;
    }
  }

  public boolean recordPacketCompletion(final String hash) {
    System.out.println("Recording complete packet: " + hash);
    if (pending.containsKey(hash)) {
      pending.remove(hash);
      return true;
    }
    return false;
  }

  public ChromeBotWorkPacket[] getPendingPackets() {
    ArrayList<ChromeBotWorkPacket> wp = new ArrayList<ChromeBotWorkPacket>();

    for (List<Object> value : pending.values()) {      
      ChromeBotWorkPacket packet = ChromeBotWorkPacket.fromByteArray((byte[]) value.get(1));
      wp.add(packet);
    }

    // convert the array list to work packets to send back to the client.
    ChromeBotWorkPacket[] ret = new ChromeBotWorkPacket[wp.size()];
    wp.toArray(ret);

    return ret;
  }
  
  public boolean reAddPendingPacket(final String id) {
    if (pending.containsKey(id)) {
      urls.add((byte[]) pending.remove(id).get(1));
      return true;
    }
    return false;
  }

  public void clearPendingQueue() {
    pending.clear();
  }
  
  class CheckPacketTimeouts extends TimerTask {
    @Override
    public void run() {
      
      System.out.println("Checking timed-out packets");
      
      SimpleDateFormat dateformat = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");  
      Calendar cal = Calendar.getInstance();
      cal.add(Calendar.MINUTE, -5); 
      
      if ( pending.size() == 0) {
        return;
      }
            
      for (Entry<String, List<Object>> value : pending.entrySet() ) {        
        
        if ( cal.getTime().after((Date)value.getValue().get(0)) ) {
          System.out.println("Packet ID: " + value.getKey() + " has timed out.");
          reAddPendingPacket(value.getKey());
        }               
      }

    }
  }
}
